无感刷新token(从后端到前端整个流程)

您所在的位置:网站首页 jwt token 过期刷新 无感刷新token(从后端到前端整个流程)

无感刷新token(从后端到前端整个流程)

2023-08-04 09:11| 来源: 网络整理| 查看: 265

前言

token刷新是前端安全中必要的一部分,本文从后端到前端整个流程介绍如何实现无感刷新token。页面代码亲自实现并跑通,请放心食用。如需源码,请在评论区留言。码字不易,点赞支持!!!

一、实现思路

通过长短token实现:短token用来请求应用数据,长token用于获取新的短token(长短指的是过期时间)

二、后端设计 后端存有两个字段,分别保存长短token,并且每一段时间更新他们 短token过期,返回 returncode:104;长token过期,返回 returncode: 108;请求成功返回returncode: 0; 请求头中pass用来接收客户端长token,请求头中authorization用来接收客户端短token 1. 搭建node服务

创建一个新文件夹,通过vscode打开,运行: 

npm init -y

安装koa

npm i koa -s

新建index.js

const Koa = require('koa') const app = new Koa(); app.use(async(ctx,next)=>{ ctx.body = "这是一个应用中间件"; await next() }) app.listen(4000,() => { console.log('server is listening on port 4000') })

安装nodemon

npm i nodemon -g

配置package.json

"dev":"nodemon index.js",

运行:npm run dev 访问:127.0.0.1:4000,可以看到页面显示这是一个应用中间件

2. 使用路由中间件

安装

npm i koa-router -S

新建routes/index.js

const router = require("koa-router")(); let accessToken = "init_s_token"; //短token let refreshToken = "init_l_token"; //长token /* 5s刷新一次短token */ setInterval(() => { accessToken = "s_tk" + Math.random(); }, 5000); /* 一小时刷新一次长token */ setInterval(() => { refreshToken = "l_tk" + Math.random(); }, 600000); /* 登录接口获取长短token */ router.get("/login", async (ctx) => { ctx.body = { returncode: 0, accessToken, refreshToken, }; }); /* 获取短token */ router.get("/refresh", async (ctx) => { //接收的请求头字段都是小写的 let { pass } = ctx.headers; if (pass !== refreshToken) { ctx.body = { returncode: 108, info: "长token过期,重新登录", }; } else { ctx.body = { returncode: 0, accessToken, }; } }); /* 获取应用数据1 */ router.get("/getData", async (ctx) => { let { authorization } = ctx.headers; if (authorization !== accessToken) { ctx.body = { returncode: 104, info: "token过期", }; } else { ctx.body = { code: 200, returncode: 0, data: { id: Math.random() }, }; } }); /* 获取应用数据2 */ router.get("/getData2", async (ctx) => { let { authorization } = ctx.headers; if (authorization !== accessToken) { ctx.body = { returncode: 104, info: "token过期", }; } else { ctx.body = { code: 200, returncode: 0, data: { id: Math.random() }, }; } }); module.exports = router;

修改index.js

//删除 app.use(async(ctx,next)=>{ ctx.body = "这是一个应用中间件"; await next() }) //新增 const index = require('./routes/index') app.use(index.routes(),index.allowedMethods()) 3. 跨域处理

安装

npm i koa2-cors

使用:

const cors = require('koa2-cors'); app.use(cors());

最终index.js文件

const Koa = require('koa') const app = new Koa(); const index = require('./routes/index') const cors = require('koa2-cors'); app.use(cors()); app.use(index.routes(),index.allowedMethods()) app.listen(4000,() => { console.log('server is listening on port 4000') })

目录结构:

重新运行 npm run dev,这时服务端已准备好

三、前端设计 1. 定义使用到的常量

新建 config/constant.js

/* localStorage存储字段 */ export const ACCESS_TOKEN = "s_tk"; //短token export const REFRESH_TOKEN = "l_tk"; //长token、 /* HTTP请求头字段 */ export const AUTH = "Authorization"; //存放短token export const PASS = "PASS"; //存放长token

新建 config/returnCodeMap.js

// 在其它客户端被登录 export const CODE_LOGGED_OTHER = 106; // 重新登陆 export const CODE_RELOGIN = 108; // token过期 export const CODE_TOKEN_EXPIRED = 104; //接口请求成功 export const CODE_SUCCESS = 0; 2. 封装服务

关键点:将token过期的请求,借助Promise将请求存进数组中,让这个Promise一直处于pending状态(即不调用resolve),当获取到新的短token时,再逐个重新请求

如果不保持promise链,就会当成一个新的请求,页面内容不会更新

安装axios

npm i axios -S

新建 service/index.js

import axios from "axios"; import { refreshAccessToken, addSubscriber } from "./refresh"; import { clearAuthAndRedirect } from "./clear"; import { CODE_LOGGED_OTHER, CODE_RELOGIN, CODE_TOKEN_EXPIRED, CODE_SUCCESS, } from "../config/returnCodeMap"; import { ACCESS_TOKEN, AUTH } from "../config/constant"; const service = axios.create({ baseURL: "//127.0.0.1:4000", timeout: 30000, }); service.interceptors.request.use( (config) => { let { headers } = config; const s_tk = localStorage.getItem(ACCESS_TOKEN); s_tk && Object.assign(headers, { [AUTH]: s_tk, }); return config; }, (error) => { return Promise.reject(error); } ); service.interceptors.response.use( (response) => { let { config, data } = response; //retry:第一次请求过期,接口调用refreshAccessToken,第二次重新请求,还是过期则reject出去 let { retry } = config; /* 延续Promise链 */ return new Promise((resolve, reject) => { if (data["returncode"] !== CODE_SUCCESS) { if ([CODE_LOGGED_OTHER, CODE_RELOGIN].includes(data.returncode)) { clearAuthAndRedirect(); } else if (data["returncode"] === CODE_TOKEN_EXPIRED && !retry) { config.retry = true; addSubscriber(() => resolve(service(config))); refreshAccessToken(); } else { return reject(data); } } else { resolve(data); } }); }, (error) => { return Promise.reject(error); } ); export default service;

新建 service/refresh.js

import service from "./index"; import { ACCESS_TOKEN, REFRESH_TOKEN, PASS } from "../config/constant"; import { clearAuthAndRedirect } from "./clear"; let subscribers = []; let pending = false; //同时请求多个过期链接,保证只请求一次获取短token export const addSubscriber = (request) => { subscribers.push(request); }; export const retryRequest = () => { subscribers.forEach((request) => request()); subscribers = []; }; export const refreshAccessToken = async () => { if (!pending) { try { pending = true; const l_tk = localStorage.getItem(REFRESH_TOKEN); if (l_tk) { /* 重新获取短token */ const { accessToken } = await service.get( "/refresh", Object.assign({}, { headers: { [PASS]: l_tk } }) ); localStorage.setItem(ACCESS_TOKEN, accessToken); retryRequest(); } return; } catch (e) { clearAuthAndRedirect(); return; } finally { pending = false; } } };

新建 service/clear.js

import {ACCESS_TOKEN} from '../config/constant' /* 清除长短token,并定位到登录页(在项目中使用路由跳转) */ export const clearAuthAndRedirect = () =>{ localStorage.removeItem(ACCESS_TOKEN) window.location.href = '/login' } 3. 使用

这里使用react实现,使用vue效果一样

//App.js

import { useState } from 'react';import service from './service/index.js';import { ACCESS_TOKEN, REFRESH_TOKEN } from './config/constant';const App = () => { const [data1, setData1] = useState(); const [data2, setData2] = useState(); const getData = () => { service.get('/getData').then((res) => { setData1(res.data.id); }); service.get('/getData2').then((res) => { setData2(res.data.id); }); }; const getToken = () => { service.get('/login').then((res) => { //存储长token localStorage.setItem(REFRESH_TOKEN, res.refreshToken); //存储短token localStorage.setItem(ACCESS_TOKEN, res.accessToken); }); }; return ( {data1}--{data2} 按钮 登录 );};export default App;

运行项目,查看效果

先点击登录按钮获取长短token,过5秒再去点击按钮,获取应用数据(上面后端设置了短token 5s过期)

页面数据能更新,并且refresh接口只调用一次

欢迎关注:之后文章会首发在云在前端公众号,未经许可禁止转载!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3